/*
 * Copyright (C) 2000-2008 Jens Nixdorf and various contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of the Software, its documentation and marketing & publicity
 * materials, and acknowledgment shall be given in the documentation, materials
 * and software packages that this Software was used.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package org.mopore.jat.gui;

import java.awt.AWTException;
import java.awt.Image;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mopore.jat.core.CoreInfo;
import org.mopore.jat.core.CorePlatformPlugin;
import org.mopore.jat.core.fadingmessage.FadingMessageWindow;
import org.mopore.jat.core.fadingmessage.calibration.CalibrationInfo;
import org.mopore.jat.core.messaging.Message;
import org.mopore.jat.plugin.IJatPlugin;
import org.mopore.jat.plugin.IPluginContextInternal;
import org.mopore.jat.plugin.IPluginFileAccess;
import org.mopore.jat.plugin.PluginException;
import org.mopore.jat.plugin.PluginFileAccessJaxB;
import org.mopore.jat.plugin.TerminatedDelegator;
import org.mopore.jat.util.ImageService;
import org.mopore.jat.util.MetaInformationService;

public class SysTrayProxy implements IThemeChangeListener {

	/**
	 * 
	 */
	private static final String CAN_NOT_RETRIEVE_CALIBRATION_INFO = "can not retrieve calibration info";

	private static final String CAN_NOT_RETRIEVE_STORED_THEME = "Can not retrieve stored theme";

	private static final String FORWARD_ERROR = "error when performing forwording event to plugin context";

	private static final int DOUBLE_CLICK_DELAY = 250;

	private final TrayIcon trayIcon;
	
	private final PopupMenu popup;
	
	private Theme theme;
	
	private BufferedImage trayImage;
	
	private Boolean clickedOnce;
	
	private final Executor singleThreadExecuter;
	
	private static final Log log = LogFactory.getLog(SysTrayProxy.class.getName());
	
	private PopUpHandler popUpHandler;

	private IPluginContextInternal pluginContext;
	
	private Queue<ShowMessageTask> queue;
	
	public SysTrayProxy() {
		
		singleThreadExecuter = Executors.newSingleThreadScheduledExecutor();
		
		clickedOnce = null;
		
		theme = retrieveStoredTheme();
		
		String imagePath = theme.getPath();
		trayImage = ImageService.load( imagePath ); 
		
		popup = new PopupMenu();
		
		trayIcon = new TrayIcon( trayImage, null, popup );
		trayIcon.setImageAutoSize( false );
		
		trayIcon.addMouseListener( new MouseAdapter() {
			
			@Override
			public void mouseClicked(MouseEvent e) {
				
				boolean left = e.getButton() == MouseEvent.BUTTON1;
				
				if ( left ) {
					
					if ( clickedOnce == null ) {
						
						singleThreadExecuter.execute(
						new Runnable() {
							
							public void run() {
								
								try {
									Thread.sleep( DOUBLE_CLICK_DELAY );
								} catch (InterruptedException e) {
									log.error( e );
									throw new InternalError( "we got a real " +
											"problem now" );
								}
								try {
									IJatPlugin plugin = 
										pluginContext.getPluginWithFocus();
									if ( clickedOnce ) {
										log.debug( "one left" );
										pluginContext.leftClicked( plugin );
									}
									else {
										log.debug( "double left" );
										pluginContext.doubleClicked( plugin );
									}
								} catch (PluginException e) {
									log.error( FORWARD_ERROR, e );
									throw new InternalError( FORWARD_ERROR );
								}
								clickedOnce = null;
								
							}
							
						});
						clickedOnce = Boolean.TRUE;
					}
					else {
						clickedOnce = Boolean.FALSE;
					}
				}
				
			}
		});
		String title = MetaInformationService.readTitle();
		trayIcon.setToolTip( title );
		queue = new LinkedList<ShowMessageTask>();
	}

	private Theme retrieveStoredTheme() {
		// Trayicon bug hack...
		try {
			IPluginFileAccess fileAccess = 
				new PluginFileAccessJaxB( CorePlatformPlugin.class );
			CoreInfo coreInfo = fileAccess.retrieve( CoreInfo.class );
			Theme theme = coreInfo.getTheme();
			return theme;
			
		} catch (Exception e) {
			log.error( CAN_NOT_RETRIEVE_STORED_THEME, e );
			throw new InternalError( CAN_NOT_RETRIEVE_STORED_THEME );
		}
	}
	

	public void updateAppearance(String text) {
		
		Image image;
		if ( text != null ) {
			 image = ImageService.createTrayIconImage( text, theme );
		}
		else {
			image = trayImage;
		}
		trayIcon.setImage( image );
		
	}


	public void attach(SystemTray tray) throws AWTException {
		tray.add( trayIcon );
	}
	
	
	public void onThemeChanged(Theme newTheme) {
		
		theme = newTheme;
		String path = theme.getPath();
		this.trayImage = ImageService.load( path );
		trayIcon.setImage( trayImage );
	}
	

	public void attachPluginContext(IPluginContextInternal pluginContext) {
		this.pluginContext = pluginContext;
		popUpHandler = new PopUpHandler( pluginContext, popup );
	}

	public void attachListeners(TerminatedDelegator terminatedDelegator ) {
		
		terminatedDelegator.addListener( popUpHandler );
	}

	public void initPopupMenu() {
		popUpHandler.buildPopUpMenu();
	}

	public void imformPluginsChange() {
		popUpHandler.setPluginsDirty();
	}

	/**
	 * @param message
	 */
	public void showMessage(Message message) {
		showMessage( message, null );
	}
	
	public void showMessage(Message message, ActionListener messageAction) {
		
		log.debug( "task to show message" );
		ShowMessageTask task = new ShowMessageTask();
		task.message = message;
		task.messageAction = messageAction;
		task.theme = theme;
		task.queue = queue;
		
		ShowMessageTask next = queue.peek();
		boolean emptyQueue = next == null;
		if ( emptyQueue ) {
			log.debug( "queue is empty. starting task." ); 
			new Thread( task ).start();
		}
		queue.add( task );
	}
	
	
	private static class ShowMessageTask implements Runnable{
		
		private Queue<ShowMessageTask> queue;
		
		private Message message;
		
		private ActionListener messageAction;
		
		private Theme theme;
		
		/**
		 * This method has been overridden.
		 * @see java.lang.Runnable#run()
		 */
		@Override
		public void run() {
			CalibrationInfo calibrationInfo;
			try {
				IPluginFileAccess fileAccess = 
					new PluginFileAccessJaxB( CorePlatformPlugin.class );
				calibrationInfo = 
					fileAccess.retrieve( CalibrationInfo.class );
			} catch (Exception e) {
				log.error( CAN_NOT_RETRIEVE_CALIBRATION_INFO, e );
				throw new InternalError( CAN_NOT_RETRIEVE_CALIBRATION_INFO );
			}
			FadingMessageWindow messageWindow = new FadingMessageWindow(
					message, calibrationInfo, theme, messageAction  );
			
			while( ! messageWindow.isDone() ) {
				try {
					Thread.sleep( 1000 );
				} catch (InterruptedException e) {
					log.error( "error during sleep!", e );
				}
			}
			
			log.debug( "message was shown" );
			// remove shown message;
			queue.poll();
			
			// check for next in queue
			ShowMessageTask next = queue.peek();
			boolean nextTask = next != null;
			if ( nextTask ) {
				log.debug( "processing next task in queue" );
				new Thread( next ).start();
			}
		}
	}
	
	
}
